Java学习笔记 - 基础篇

访问修饰符

访问修饰符 同一个类 同包 不同包,子类 不同包,非子类
private
默认
protected
public

Java中对象及对象引用变量

Java中的对象的引用和对象有些类似C++中的地址和地址的指向的关系。

1
2
3
4
5
public class Information {
int name;
int grade;
int point;
}

我们先定义一个Information类。
然后我们创建一个Infromation对象:

1
Information stu1 = new Information();

这个操作的本质是:

  1. new Information是以Information类为模板,在堆空间里创建一个Information类对象。
  2. 末尾的()意味着,在对象创建后,立即调用Information类的构造函数,对刚生成的对象进行初始化。构造函数是肯定有的。如果你没写,Java会给你补上一个默认的构造函数。
  3. Information stu1是创建一个Information类引用变量。
  4. =使对象引用指向刚创建的那个Information对象。

我们可以把stu1类比于C++中的地址。

1
Information stu2 = stu1;

此时stu2和stu1都指向刚创建的那个Information对象。

1
2
Information stu2 = new Information();
stu1 = stu2;

这样之前的那个Information类就会被回收。

Integer和int

Java的两种数据类型:

  • 基本类型:基本数据类类型存的是数值本身。有byte, short, int, long, float, double, boolean, char。
  • 引用类型:引用类型变量在内存放的是数据的引用。比如上面定义的Information,以及标题提及的Integer。

下面我们重点讨论”==”这个运算符。
先给出如下表达式的值:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
int a = 1000;
int b = 1000;
Integer c = 10;
Integer d = 10;
Integer e = 1000;
Integer f = 1000;
Integer g = new Integer(10);
Integer h = new Integer(10);
System.out.println(a == b); //true
System.out.println(c == d); //true
System.out.println(c.equals(d)); //true
System.out.println(e == f); //false
System.out.println(e.equals(f)); //true
System.out.println(g == h); //false
System.out.println(g.equals(h)); // true
System.out.println(a == e); //true

int是基本类型,他的 == 比较是基于数值本身的。
Integer是引用类型,本质是个类,因此它还有equals()函数可以进行比较,也可以用 == 来进行比较。equals()比较是基于数值的; Integer的 == 比较是基于引用地址的。
现在又有一个问题,为什么c == d是true呢?
原因是这样的:
当我们给一个Integer赋予一个int类型的时候会调用Integer的静态方法valueOf()。也就是说,Integer c = 10;相当于Integer c = Integer.valueOf(10);
jdk1.5的时候引进了自动拆包,装包这个功能。上面这种情况就是自动打包出现的。
具体来看看Integer.valueOf()的源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
/*
* Cache to support the object identity semantics of autoboxing for values between
* -128 and 127 (inclusive) as required by JLS.
*
* The cache is initialized on first usage. The size of the cache
* may be controlled by the {@code -XX:AutoBoxCacheMax=<size>} option.
* During VM initialization, java.lang.Integer.IntegerCache.high property
* may be set and saved in the private system properties in the
* jdk.internal.misc.VM class.
*/

private static class IntegerCache {
static final int low = -128;
static final int high;
static final Integer cache[];

static {
// high value may be configured by property
int h = 127;
String integerCacheHighPropValue =
VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
if (integerCacheHighPropValue != null) {
try {
int i = parseInt(integerCacheHighPropValue);
i = Math.max(i, 127);
// Maximum array size is Integer.MAX_VALUE
h = Math.min(i, Integer.MAX_VALUE - (-low) -1);
} catch( NumberFormatException nfe) {
// If the property cannot be parsed into an int, ignore it.
}
}
high = h;

cache = new Integer[(high - low) + 1];
int j = low;
for(int k = 0; k < cache.length; k++)
cache[k] = new Integer(j++);
// range [-128, 127] must be interned (JLS7 5.1.7)
assert IntegerCache.high >= 127;
}

private IntegerCache() {}
}

从上面我们可以知道给Interger赋予的int数值在-128 - 127的时候,直接从cache中获取,这些cache引用对Integer对象地址是不变的,但是不在这个范围内的数字,则new Integer(i)这个地址是新的地址,不可能一样的。
所以上个例子中c和d都是IntegerCache的引用,故c == d为true。而cache中没有1000,故e == f为false。
intInteger比较时,Integer会自动拆包,转化为int和其比较。

final关键字

  • 被final修饰的类不可以被继承,final类中的成员变量可以根据需要设为final,但是要注意final类中的所有成员方法都会被隐式地指定为final方法。
  • 被final修饰的方法不可以被重写
  • 被final修饰基本数据类型和String类型的变量为常量,不可以被修改,且在编译阶段会存入调用类的常量池中
  • 被final修饰的引用类型变量不可以改变引用的指向,指向的对象的内容是可以改变的
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class test {
final int N = 500;
//N = 1000; 报错
public static void main(String[] args) {
final Info QAQ = new Info();
Info QAQ2 = new Info();
//QAQ = QAQ2; 报错
QAQ.print();
QAQ.a = 100; // OK
QAQ.print();
}
}
class Info {
public int a = 1, b = 2, c = 3;
public void print() {
System.out.println(a + " " + b + " " + c);
}
}

输出为

1
2
1 2 3
100 2 3

static关键字(待重新整理)

首先说this
this首先是一个对象,它代表调用这个函数的对象。
根据面向对象的基本语法,每当调用变量或者函数的时候,都要按照类名.变量(函数) 的格式来调用。
在不会产生混淆的地方, this是可以省略的。

1
2
3
4
5
6
7
8
9
10
11
class Name {
String name;
void QAQ() {
System.out.println(this.name);
}
}
/*
定义Name n1, n2;
对于n1.QAQ(),this.name为n1.name。
对于n2.QAQ(),this.name为n2.name。
*/

static 方法一般称作静态方法,由于静态方法不依赖于任何对象就可以进行访问,因此对于静态方法来说,是没有this的,因为它不依附于任何对象,既然都没有对象,就谈不上this了。并且由于这个特性,在静态方法中不能访问类的非静态成员变量和非静态成员方法,因为非静态成员方法/变量都是必须依赖具体的对象才能够被调用,但是可以定义一个对象,通过对象来访问。

抽象方法和抽象类

一个类没有包含足够的信息来描绘一个具体的对象,这样的类就是抽象类。
当你想构造一个父类方法,他的具体实现由子类来定义,那么这个方法可以定义为抽象方法,当然,普通方法也可以。
抽象类和抽象方法用abstrct来修饰。
抽象类和方法的性质:

  1. 抽象类不能被实例化,如果被实例化,就会报错,编译无法通过。只有抽象类的非抽象子类可以创建对象。

  2. 抽象类中不一定包含抽象方法,但是有抽象方法的类必定是抽象类。

  3. 抽象类中的抽象方法只是声明,不包含方法体,就是不给出方法的具体实现也就是方法的具体功能。

  4. 构造方法,类方法(用static修饰的方法)不能声明为抽象方法。

  5. 抽象类的子类必须给出抽象类中的抽象方法的具体实现,除非该子类也是抽象类。

  6. 抽象方法必须为public或者protected,缺省默认public。

1
2
3
4
5
6
7
8
9
10
11
12
abstract class func {
abstract void function1();
void function2() {
System.out.println("2 in func1");
}
private void function3() {
System.out.println("3 in func1");
}
public void function4() {
System.out.println("4 in func1");
}
}

其中function3()会有一个warning:

The method function3() from the type func is never used locally

再建立一个func2类继承于func:

1
2
class func2 extends func {
}

会有一个error:

The type func2 must implement the inherited abstract method func.function1()

就是说我们必须重写function1()。
重写:

1
2
3
4
5
6
7
8
class func2 extends func {
void function1() {
System.out.println("1 in func2");
}
void function2() {
System.out.println("2 in func2");
}
}

定义:

1
2
3
4
5
func2 f = new func2();
f.function1();
f.function2();
//f.function3(); ERROR
f.function4();

输出:

1
2
3
1 in func2
2 in func2
4 in func1

抽象类和接口

接口(interface) 在java中是一个抽象类型,是抽象方法的集合。一个类通过继承接口的方式,从而继承接口的抽象方法。
接口的定义方式如下:

1
2
interface InterfaceName {
}

接口中可以含有变量和方法。但是要注意,接口中的变量会被隐式地指定为public static final变量(final类型详见第4节),而方法会被隐式地指定为public abstract方法。
要让一个类遵循某组特地的接口需要使用implements关键字,具体格式如下:

1
2
class ClassName implements Interface1,Interface2,...{
}

另外,一个接口必须声明在同名的.java中。

同时,接口还可以继承接口。
实例:
test.java

1
2
3
4
5
6
7
8
package test;


interface test {
void f1();
void f2();
void f3();
}

test2.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package test;

public interface test2 extends test{
int a = 10;
abc b = new abc();
void f4();
}
class abc {
int len = 5;
void a() {
System.out.println("a");
}
void b() {
System.out.println("b");
}
void c() {
System.out.println("c");
}
}

Test3.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
package test;

public class Test3 implements test2 {
/*
* f1到f4都是test2接口的实现
*/
public void f1() {
System.out.println("1");
}
public void f2() {
System.out.println("2");
}
public void f3() {
System.out.println("3");
}
public void f4() {
System.out.println("4");
}
public static void main(String[] args) {
Test3 t = new Test3();
/*
* a被final修饰
* 若a = 100; 会报错The final field test2.a cannot be assigned
*/
System.out.println(a);
/*
* b是被final修饰的一个类。
* final类中的变量不是不被final修饰,而方法被隐性转为final。
* 因此b.len可以被改变。
*/
b.len = 1;
System.out.println(b.len);
/*三个接口中的变量的方法*/
b.a();b.b();b.c();
/*
* 四个接口方法
*/
t.f1();
t.f2();
t.f3();
t.f4();
}
}

总的来说,抽象类和接口的区别有:抽象类是一个类,里边可以包含抽象方法和非抽象方法、抽象变量和非抽象变量,而接口是方法的集合,只包含方法。一个类可以有多个接口,但只能有一个父类。

可变对象和不可变对象

Copy from here

概念

不可变对象(Immutable Objects):对象一旦被创建它的状态(对象的数据,也即对象属性值)就不能改变,任何对它的改变都应该产生一个新的对象。比如String。
可变对象(Mutable Objects):相对于不可变类,可变类创建实例后可以改变其成员变量值,开发中创建的大部分类都属于可变类。比如StringBuilder。

如何构造不可变类

  1. 类添加final修饰符,保证类不被继承。
    如果类可以被继承会破坏类的不可变性机制,只要继承类覆盖父类的方法并且继承类可以改变成员变量值,那么一旦子类以父类的形式出现时,不能保证当前类是否可变。
  2. 保证所有成员变量必须私有,并且加上final修饰。
    通过这种方式保证成员变量不可改变。但只做到这一步还不够,因为如果是对象成员变量有可能再外部改变其值。所以第4点弥补这个不足。
  3. 不提供改变成员变量的方法,包括setter。
    避免通过其他接口改变成员变量的值,破坏不可变特性。
  4. 通过构造器初始化所有成员,进行深拷贝(deep copy)。
    如果构造器传入的对象直接赋值给成员变量,还是可以通过对传入对象的修改进而导致改变内部变量的值。例如:
    1
    2
    3
    4
    5
    6
    public final class MyImmutableDemo {  
    private final int[] myArray;
    public MyImmutableDemo(int[] array) {
    this.myArray = array.clone();
    }
    }